package com.mspbots.teams.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mspbots.api.RestMessageObject;
import com.mspbots.api.client.CoreClient;
import com.mspbots.api.client.ScriptClient;
import com.mspbots.api.config.ApiProperties;
import com.mspbots.teams.model.*;
import com.mspbots.teams.reports.TeamsMessageReportsService;
import com.mspbots.teams.service.MessageService;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.mspbots.common.TriggerEvent.TEAMS_COMMAND;

@Slf4j
@Service
public class MessageServiceImpl implements MessageService {

    public static final String REDIS_KEY_PREFIX_SYSTEM = "MSPBOTS:CONFIG:SYSTEM:";
    public static final String REDIS_KEY_PREFIX_TENANT = "MSPBOTS:CONFIG:TENANT:";
    public static final String CONFIGURATION_MESSAGE_SWITCH = "messages_switch";
    public static final String CONFIGURATION_MESSAGE_FORWARDS = "messages.forwards";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ApiProperties apiProperties;
    @Autowired
    private CoreClient coreClient;
    @Autowired
    private ScriptClient scriptClient;

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RabbitMessagingTemplate messagingTemplate;

    @Autowired
    private TeamsMessageReportsService teamsMessageReportsService;

    @Override
    public JsonNode send(TeamsMessageDTO teamsMessage) {

        log.debug("teams messages send :: {}",this.objectMapper.convertValue(teamsMessage,JsonNode.class));
        Messages messages = Messages.builder()
                .tenantId(teamsMessage.getTenantId())
                .userId(teamsMessage.getTenantUserId())
                .timeEntryId(teamsMessage.getBusinessId())
                .escalation(StringUtils.isEmpty(teamsMessage.getEscalation())?false:teamsMessage.getEscalation())
                .ruleId(teamsMessage.getRuleId())
                .message(teamsMessage.getMessage())
                .type(StringUtils.isEmpty(teamsMessage.getType())?"user":teamsMessage.getType())
                .status("Ready")
                .forwardStatus("Ready")
                .createDate(LocalDateTime.now())
                .expireDate(LocalDateTime.now().plusHours(8))
                .teamsUserId(teamsMessage.getTeamsUserId())
                .businessId(teamsMessage.getBusinessId())
                .businessType(teamsMessage.getBusinessType())
                .priority(1)
                .build();

        ObjectNode result = objectMapper.createObjectNode();

        boolean messageRepeat = false;
        String frequency = StringUtils.isEmpty(teamsMessage.getFrequency()) ? "once" : teamsMessage.getFrequency();
        Map<String, Object> repeatParams = new HashMap<>(16);
        repeatParams.put("userId", messages.getUserId());
        repeatParams.put("ruleId", messages.getRuleId());
        switch (frequency) {
            case "ignore":
                break;
            case "hours":
                Assert.notNull(teamsMessage.getHours());
                repeatParams.put("createDateFrom",LocalDateTime.now().minusHours(teamsMessage.getHours()));
                messageRepeat = checkRepeat(repeatParams);
                break;
            case "week" :
                LocalDate localDate = LocalDate.now().minusDays(LocalDate.now().getDayOfWeek().getValue()-1);
                repeatParams.put("createDateFrom",LocalDateTime.of(localDate, LocalTime.MIN));
                messageRepeat = checkRepeat(repeatParams);
                break;
            case "schedule" :
            case "once" :
            default:
                Assert.notNull(messages.getBusinessId());
                repeatParams.put("businessId",messages.getBusinessId());
                messageRepeat = checkRepeat(repeatParams);
                break;
        }

        if (!messageRepeat) {
            //result = coreClient.post("/teams/messages", messages).block();
            messages.setCreateDate(getLocalDate());
            String countEscalationStr = coreClient.post("/teams/messages/insertMessages", messages,String.class).block();

            String[] arr = countEscalationStr.split("::");
            messages.setId(Long.parseLong(arr[1]));
            messages.setMessage(messages.getMessage().replace("{countMessage}",arr[0]+""));

            if (teamsMessage.getSend() !=null  && teamsMessage.getSend()) {

                if (teamsMessage.getTeamsUserId() != null && !"null".equalsIgnoreCase(teamsMessage.getTeamsUserId())) {
                    Messages savedMessage = this.objectMapper.convertValue(messages, Messages.class);
                    messagingTemplate.convertAndSend("msp.message.teams", "", savedMessage);
                    result.put("tenantId",messages.getTenantId());
                }
            }
        }

        // notify reports
        /*
        JsonNode userMapping=getUserMappingDetail(messages.getTeamsUserId());
        teamsMessageReportsService.sent_message(messages.getTenantId(),userMapping.get("userName").asText());
        teamsMessageReportsService.botsWorking_message(messages.getTenantId(),userMapping.get("userName").asText(),messages.getRuleId());
         */
        return result;
    }


    @Override
    public JsonNode saveMessage(TeamsMessageDTO teamsMessage) {

        log.debug("teams saveMessage :: {}",this.objectMapper.convertValue(teamsMessage,JsonNode.class));
        Messages messages = Messages.builder()
                .tenantId(teamsMessage.getTenantId())
                .userId(teamsMessage.getTenantUserId())
                .timeEntryId(teamsMessage.getBusinessId())
                .escalation(StringUtils.isEmpty(teamsMessage.getEscalation())?false:teamsMessage.getEscalation())
                .ruleId(teamsMessage.getRuleId())
                .message(teamsMessage.getMessage())
                .type(StringUtils.isEmpty(teamsMessage.getType())?"user":teamsMessage.getType())
                .status("Received")
                .forwardStatus("Received")
                .createDate(LocalDateTime.now())
                .expireDate(LocalDateTime.now().plusHours(8))
                .teamsUserId(teamsMessage.getTeamsUserId())
                .businessId(teamsMessage.getBusinessId())
                .businessType(teamsMessage.getBusinessType())
                .priority(1)
                .build();

        ObjectNode result = objectMapper.createObjectNode();

        String countEscalationStr = coreClient.post("/teams/messages/insertMessages", messages,String.class).block();

        String[] arr = countEscalationStr.split("::");

        result.put("messageId",Long.parseLong(arr[1]));

        return result;
    }

    private LocalDateTime getLocalDate(){
        Date createDate = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String imptimeBegin = null;
        Calendar cal = Calendar.getInstance();

        cal.setTime(createDate);
        if (cal.get(Calendar.DAY_OF_WEEK) == 1) {
            cal.add(Calendar.DATE, -6);
            imptimeBegin = sdf.format(cal.getTime());
        } else if (cal.get(Calendar.DAY_OF_WEEK) == 2) {
            imptimeBegin = sdf.format(cal.getTime());
        } else {
            cal.add(Calendar.DATE, 2 - cal.get(Calendar.DAY_OF_WEEK));
            imptimeBegin = sdf.format(cal.getTime());
        }
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        return LocalDate.parse(imptimeBegin, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay();
    }

    private boolean checkRepeat(Map<String, Object> repeatParams) {
        int countDefault= coreClient.get("/teams/messages/countMessagesByForRule", repeatParams, Integer.class).block();
        log.debug("new message param : {} {} ", repeatParams.get("businessId"),countDefault);
        return countDefault > 0;
    }

    @Override
    public RestMessageObject command(TeamsCommand teamsCommand) {
        log.info("teams command : {}", teamsCommand);
        Map<String, Object> userMappingParam = Map.of(
                "teamsUserId",teamsCommand.getTeamsUserId()
        );
        JsonNode userMapping = coreClient.get("/user-mapping/command-detail", userMappingParam).block();
        log.debug("teams command userMapping: {}", userMapping);

        if (!ObjectUtils.isEmpty(userMapping)) {
            TriggerEventDTO triggerEventDTO = new TriggerEventDTO();
            try {
                String jsonStr = objectMapper.writeValueAsString(teamsCommand);
                JsonNode payload = objectMapper.readTree(jsonStr);

                triggerEventDTO.setTenantId(userMapping.get("tenantId").asLong());
                triggerEventDTO.setEventName(TEAMS_COMMAND.getEvent());
                triggerEventDTO.setScope(TEAMS_COMMAND.getScope());
                triggerEventDTO.setPayload(payload);
                triggerEventDTO.setUserInfo(userMapping);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            if (teamsCommand.getSync() == null || teamsCommand.getSync()) {
                JsonNode result = scriptClient.post("execute/event", triggerEventDTO).block();
                log.debug("teamsCommand::{}",teamsCommand);
                log.debug("result :{} ", result);
                List<String> ss = objectMapper.convertValue(result, ArrayList.class);
                String returnStr =  ss.stream().collect(Collectors.joining());
                log.info("returnStr [{}] ", returnStr);
                teamsCommand.setFromType("command type");
                sendExceptionLog(teamsCommand,userMapping.get("tenantId").asInt());
                return RestMessageObject.ok(returnStr);
            } else {
                this.messagingTemplate.convertAndSend("msp.trigger.event", "", triggerEventDTO);
                log.debug("cw msp.trigger.event TeamsCommand [{}] ", this.objectMapper.convertValue(triggerEventDTO,JsonNode.class));
                log.debug("teamsCommand::{}",teamsCommand);
                sendExceptionLog(teamsCommand,userMapping.get("tenantId").asInt());
                return RestMessageObject.ok("Mspbots send message in queue!");
                    // notify reports
                    //teamsMessageReportsService.received_message(triggerEventDTO.getTenantId(),triggerEventDTO.getUserInfo().get("userName").asText());
            }
        }else{
            log.info("Mspbots can't find your user info. teams_user_id:{}",teamsCommand.getTeamsUserId());
            return RestMessageObject.ok("Mspbots can't get your teams info.");
        }
    }

    private void sendExceptionLog(TeamsCommand teamsCommand,Integer tenantId){
        if(!ObjectUtils.isEmpty(teamsCommand)
                && !ObjectUtils.isEmpty(teamsCommand.getMessage())
                && teamsCommand.getMessage().trim().toLowerCase().equals("qa")){

            log.debug("teamsCommand::fromType:{}",teamsCommand.getFromType());

            this.messagingTemplate.convertAndSend("exception.log", ExceptionLog.builder()
                    .tenantId(tenantId).name("TeamsCommand")
                    .status(200).type("qa check").msg(teamsCommand.getFromType()+":qa "+":teamsUserId:"+teamsCommand.getTeamsUserId())
                    .build());
        }

    }

    @Override
    public boolean sendToTeams(Messages messages) {
        boolean sendSuccess = false;
        boolean switchON = checkSwitch(messages);
        if (switchON) {
            Map<String, Object> params = new HashMap<>(16);
            params.put("tenantId", messages.getTenantId());
            params.put("tenantUserId", messages.getUserId());
            JsonNode tenantUserConfiguration = coreClient.get("/tenant/user-configuration/keyword/messages_switch", params).block();
            if (tenantUserConfiguration == null || "on".equalsIgnoreCase(tenantUserConfiguration.get("keywordValue").asText())) {
                if (!StringUtils.isEmpty(messages.getTeamsUserId())) {
                    messages.setMessage(messages.getMessage() + (suffixBugReport(messages,"send")));
                    if (postSend(messages)) {
                        sendSuccess = true;
                        Messages updateMessage =  Messages.builder().id(messages.getId()).status("Received").sentTime(LocalDateTime.now()).build();
                        coreClient.put("/teams/messages/"+updateMessage.getId(), updateMessage).block();
                    }
                }
            }
        }
        return sendSuccess;
    }

    private boolean postSend(Messages messages) {
        boolean sendResult = false;
        HashMap<String, Object> params = new HashMap<>(16);
        //params.put("userid", "4d387925-1491-4248-a8c1-135a780fe3d2");
        //params.put("message", messages.getMessage() + "test by zj");
        params.put("message", messages.getMessage());
        params.put("userid", messages.getTeamsUserId());
        String result = "";
        MessagesLog messagesLog = MessagesLog.builder().messageId(messages.getId()).params(params.toString()).createTime(LocalDateTime.now()).build();
        if (!StringUtils.isEmpty(messages.getTeamsUserId())) {
            result = HttpUtil.post(apiProperties.getTeamsApiUrl() + "/notify", params);
            JSONObject jsonObject = JSONUtil.parseObj(result);
            log.debug("send msg {} to {} {} {} ", messages.getId(), params.get("userid"), jsonObject.getStr("errorcode"), jsonObject.getStr("message"));
            sendResult =  "1".equals(jsonObject.getStr("errorcode"));
            /*if(sendResult){
                messagesLog.setResultCode(jsonObject.getInt("errorcode"));
                messagesLog.setResultMsg(jsonObject.getStr("message"));
                messagingTemplate.convertAndSend("msp.message.teams.log", "", messagesLog);
            }*/

            //messagesLog.setResultCode(jsonObject.getInt("errorcode"));
            //messagesLog.setResultMsg(jsonObject.getStr("message"));

            // notify reports

            /*
            JsonNode userMapping=getUserMappingDetail(messages.getTeamsUserId());
            teamsMessageReportsService.received_message(userMapping.get("tenantId").asLong(),userMapping.get("userName").asText());
             */
        } else {
            log.debug("msg {} has no teams_user_id ", messages.getId());
            //messagesLog.setResultCode(0);
            //messagesLog.setResultMsg("teams_user_id is null");
        }

        //messagingTemplate.convertAndSend("msp.message.teams.log", "", messagesLog);
        return sendResult;
    }

    private boolean checkSwitch(Messages messages) {
        boolean switchON = false;
        String systemSwitch = stringRedisTemplate.opsForValue().get(REDIS_KEY_PREFIX_SYSTEM + CONFIGURATION_MESSAGE_SWITCH);
        if (systemSwitch == null || "on".equalsIgnoreCase(systemSwitch)) {
            String tenantSwitch = stringRedisTemplate.opsForValue().get(REDIS_KEY_PREFIX_TENANT + messages.getTenantId() + ":" + CONFIGURATION_MESSAGE_SWITCH);
            if (tenantSwitch == null || "on".equalsIgnoreCase(tenantSwitch)) {
                switchON = true;
            }
        }
        return switchON;
    }

    @Override
    public boolean sendToTeamsForward(Messages messages) {
        String suffixBugReport = suffixBugReport(messages, "sendForward");
        messages.setMessage(messages.getMessage() + suffixBugReport);
        Boolean systemUpdate = false;
        Boolean tenantUpdate = false;
        String systemForwardIds = stringRedisTemplate.opsForValue().get(REDIS_KEY_PREFIX_SYSTEM + CONFIGURATION_MESSAGE_FORWARDS);

        // forward all message based on system configuration
        String[] forwardSystemUsers = StringUtils.isEmpty(systemForwardIds) ? new String[0] : systemForwardIds.split(",");


        for (String user : forwardSystemUsers) {
            if (!StringUtils.isEmpty(user)) {
                try{
                    messages.setTeamsUserId(user);
                    systemUpdate = postSend(messages) || systemUpdate;
                }catch (Exception e){
                    log.error("send system forward error::{}", user);
                    e.printStackTrace();
                }
            }
        }
        String tenantForwardIds = stringRedisTemplate.opsForValue().get(REDIS_KEY_PREFIX_TENANT + messages.getTenantId() + ":" + CONFIGURATION_MESSAGE_FORWARDS);
        // forward tenant message base on tenant configuration
        if (!StringUtils.isEmpty(tenantForwardIds)) {
            String[] forwardTenantUsers = StringUtils.isEmpty(tenantForwardIds) ? new String[0] : tenantForwardIds.split(",");
            for (String user : forwardTenantUsers) {
                if (!StringUtils.isEmpty(user)) {
                    try{
                        messages.setTeamsUserId(user);
                        tenantUpdate = postSend(messages) || tenantUpdate;
                    }catch (Exception e){
                        log.error("send tenant forward error::{}", user);
                        e.printStackTrace();
                    }

                }
            }
        }

        Messages updateMessage = null;
        if (systemUpdate || tenantUpdate) {
            try{
                updateMessage =  Messages.builder().id(messages.getId()).forwardStatus("Received").build();
                coreClient.put("/teams/messages/"+updateMessage.getId(), updateMessage).block();
            }catch (Exception e){
                log.error("sendToTeamsForward::::update::error::: {}", updateMessage.getId());
                e.printStackTrace();
            }
        }else{
            log.error("systemUpdate tenantUpdate failed.");
        }
        return systemUpdate || tenantUpdate;
    }

    @Override
    public boolean reply(Messages messages) {
        return postSend(messages);
    }


    public String suffixBugReport(Messages messages, String from) {
        StringBuilder sb = new StringBuilder().append("businessId=").append(messages.getBusinessId())
                .append("&messageId=").append(messages.getId())
                .append("&tenantId=").append(messages.getTenantId())
                .append("&tenantUserId=").append(messages.getUserId())
                .append("&timeEntryId=").append(StringUtils.isEmpty(messages.getTimeEntryId())?"":messages.getTimeEntryId())
                .append("&from=").append(from);
//        return "&nbsp;<span style='color:#999d9c;font-size:10px;'>-- [<a href='" + apiProperties.getPortalUrl() + "/bug?params=" +
//                Base64Utils.encodeToString(sb.toString().getBytes(StandardCharsets.UTF_8))
//                + "' >Report an error about this message</a>]</span>";

        return "--<span style='color:#999d9c;font-size:10px;'> Please <a href='" + apiProperties.getPortalUrl() + "/bug?params=" +
                Base64Utils.encodeToString(sb.toString().getBytes(StandardCharsets.UTF_8))
                + "' >let us know </a>if you think there's a mistake.</span>";
    }


    private JsonNode getUserMappingDetail(String teamsUserId){
        Map<String, Object> userMappingParam = Map.of(
               "teamsUserId",teamsUserId
        );
        JsonNode userMapping = coreClient.get("/user-mapping/detail", userMappingParam).block();
        if (!ObjectUtils.isEmpty(userMapping)) {
            log.debug("user info : {}", userMapping);
            return userMapping;
        }
        return null;
    }
}
